#include <string>
#include <algorithm>
#include <cassert>
#include <ctime>
#include <iostream>
#include <sstream>
#include <hash_map>
#include "misc.h"
#include "BrainSwitch.h"
#include "movegen.h"
#include "Engine.h"
#include "Types.h"
#include "thread.h" //<-- to allow acces to global statuses
//#using <mscorlib.dll>
//using namespace System;
//using namespace System::Threading;

//For global access
Engine ChessEngine;

Engine::Engine(void)
{
	init();
	
}

void Engine::init()
{
	lock_init(this->nodeQueueLock);
	brainStatus = INITIALIZING;

    // Smart timing (default = true)
    isSmartTiming = false;
    maxDepth = -1;

    // TOD debug init = Blitz 2'+6"/ply - to be removed
	if(debugMode){
		SetStartPosition();
		this->wtime = 120000;
		this->btime = 120000;
		this->winc = 6000;
		this->binc = 6000;
		//this->SetMovetimeMode(6000);
	}
	thinkDelay = 6000;
	SetStartPosition();
	
	// TODO: Do this action somewhere else
	// (retrieve engine options)
	Threads.init();

	brainStatus = PAUSED;
}

Engine::~Engine(void)
{
	lock_release(this->nodeQueueLock);
}

Move* Engine::GetBestMove(){
	if (debugMode)
    {
        cout << "###########################################" << endl;
        //foreach (ArrayBoardNode n in possibleNodes)
        for (list<ChildrenNode>::iterator node = possibleNodes.begin(); node != possibleNodes.end(); node++)
		{
            cout << move_to_uci(*(node->GetPreviousMove()),false) << " score=" << node->GetScore() << endl;
        }
        cout << "###########################################" <<endl;
    }
	cout << "[DEBUG] possibleNodes=" << possibleNodes.size() << endl;
	if (!possibleNodes.empty())
    {
		return GetBestNode()->GetPreviousMove();
    }

	return NULL;
}

void Engine::SetStartPosition(){
	currPos.from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",false);
}

void Engine::SetPosition(const std::string& fen){
	currPos.from_fen(fen,false);
}

void Engine::SetPonderLimit(bool ponder){
	// TODO : complete this function
	//ponder =;
}

bool Engine::IsStopOnPonderHit()
{
	// TODO : complete this function
	//return
	return false;
}

void Engine::FlipBoard(){
	currPos.flip_me();
}

void Engine::ReadEvaluation()
{
	// TODO : complete this function
	//read_evaluation_uci_options(pos.side_to_move());
}

void Engine::TraceEvaluate()
{
	// TODO : complete this function
	///cout << trace_evaluate(pos) << endl;
}

char* Engine::GetEngineInfo()
{
	return "id name OliveChess 0.2 BitMaster (C++ version)\n"
		"id author Olivier Herau, France\n";
}

char* Engine::GetEngineOptions()
{
	// TODO : auto-generate this part ???
	return "option name Pawn Value type spin default 80 min 0 max 2000\n"
		"option name Knight Value type spin default 325 min 0 max 2000\n"
        "option name Bishop Value type spin default 325 min 0 max 2000\n"
		"option name Rook Value type spin default 500 min 0 max 2000\n"
        "option name Queen Value type spin default 975 min 0 max 2000\n"
		"option name Smart Timing type check default false\n";
}

/*
void Engine::DoMove(Move m, StateInfo& state){
	// TODO : complete this function

}*/

bool Engine::IsValidOption(string name){
		// TODO : complete this function
	return false;
}

void Engine::SetOption(string name, string value){
		// TODO : complete this function
}

void Engine::SetLimitsInfinite(bool b){
			// TODO : complete this function
}

void Engine::SetLimitsPonder(bool b){
			// TODO : complete this function
}

void Engine::Bench(string part){
// TODO : complete this function
	if( part == "GetPossibleMoves" ){
		BenchGetPossibleMoves();
	}
	else if ( part == "GetLegalMoves" ){
		BenchGetLegalMoves();
	}
	else if ( part == "ThreadsManager" ){
		BenchThreadManager();
	} 
	else
	{
		// Default : include all the bench
		BenchGetPossibleMoves();
		BenchGetLegalMoves();
		BenchThreadManager();
	}
}

void Engine::BenchGetLegalMoves(){
		// TODO : complete this function

}

void Engine::BenchGetPossibleMoves(){
		// TODO : complete this function
}

void Engine::BenchThreadManager(){
		// TODO : complete this function
}

void Engine::Test(string part){
// TODO : complete this function
	if( part == "GetPossibleMoves" ){
		//TestGetPossibleMoves();
	}
	else if ( part == "GetLegalMoves" ){
		TestGetLegalMoves();
	}
	else if ( part == "ThreadsManager" ){
		//TestThreadManager();
	} 
	else
	{
		// Default : include all the bench
		//TestGetPossibleMoves();
		TestGetLegalMoves();
		//TestThreadManager();
	}
}

void Engine::TestGetLegalMoves() {

	//Hashtable testPositions = new Hashtable();
	stdext::hash_map<string, string> testPositions;
	//OK
	testPositions["k6K/8/8/8/8/8/PPPPPPPP/8 w - - 1 1"] = "a2a3 a2a4 b2b3 b2b4 c2c3 c2c4 d2d3 d2d4 e2e3 e2e4 f2f3 f2f4 g2g3 g2g4 h2h3 h2h4 h8g8 h8g7 h8h7"; //w pawn moves
	testPositions["k1K5/p7/8/8/8/8/8/8 b - - 1 1"] = "a7a6 a7a5"; //b basic pawn moves
    testPositions["r3k2r/8/8/8/8/p6p/P6P/R3K3 w KQkq - 1 1"] = "a1b1 a1c1 a1d1 e1d1 e1d2 e1e2 e1f2 e1f1 e1c1";
    testPositions["K6k/8/8/8/8/8/8/8 b - - 0 1"] =  "h8g8 h8g7 h8h7";
    testPositions["8/8/8/4N3/8/8/8/8 w - - 10 10"] = "e5f7 e5g6 e5g4 e5f3 e5d3 e5c4 e5c6 e5d7"; // knight moves
    
	//knight (h8g8 h8g7 h8h7 = king part)
	testPositions["k6K/8/8/8/8/8/8/N8 w - - 1 1"] = "a1b3 a1c2     h8g8 h8g7 h8h7"; //w pawn moves
	//bishop (h8g8 h8g7 h8h7 = king part)
	testPositions["k6K/8/8/8/8/8/8/1B6 w - - 1 1"] = "b1c2 b1d3 b1e4 b1f5 b1g6 b1h7 b1a2    h8g8 h8g7 h8h7"; //w pawn moves
	//bishop (h8g8 h8g7 h8h7 = king part)
	testPositions["k6K/8/8/8/8/8/8/1R6 w - - 1 1"] = "b1a1 b1c1 b1d1 b1e1 b1f1 b1g1 b1h1 b1b2 b1b3 b1b4 b1b5 b1b6 b1b7 b1b8     h8g8 h8g7 h8h7"; 

	//queen (h8g8 h8g7 h8h7 = king part)
	testPositions["k6K/8/8/8/8/8/8/1Q6 w - - 1 1"] = "b1c2 b1d3 b1e4 b1f5 b1g6 b1h7 b1a2 b1a1 b1c1 b1d1 b1e1 b1f1 b1g1 b1h1 b1b2 b1b3 b1b4 b1b5 b1b6 b1b7 b1b8     h8g8 h8g7 h8h7"; 

	//pawn promotion (h8g8 h8g7 h8h7 = king part)
	testPositions["k6K/3P4/8/8/8/8/8/8 w - - 1 1"] = "d7d8q d7d8r d7d8b d7d8n      h8g8 h8g7 h8h7"; 

	//king vs king ()
	testPositions["K1k/8/8/8/8/8/8/8 w - - 1 1"] = "a8a7";


	testPositions["rnbqkbnr/4pQ2/2pp3p/6B1/p1BPP3/2N2NP1/PPP2PP1/R3K2R b KQkq - 0 11"] = "e8d7";
	testPositions["4kb1r/4b3/8/7B/8/8/8/6K1 b k - 0 19"] =  "h8h5 e8d8 e8d7";
    testPositions["rB2kbbr/1p6/8/p6Q/2PPP3/8/PP3PPP/R4RK1 b - - 0 19"] =  "h8h5 g8f7 e8e7 e8d7 e8d8";

	testPositions["rn2kbnr/2Nbpppp/8/2pP4/3P1B2/P7/P1P2PPP/R2QKB1R b KQkq - 1 1"] =  "e8d8";
    testPositions["rnbqkbnr/4pQ2/2pp3p/6B1/p1BPP3/2N2NP1/PPP2PP1/R3K2R b KQkq - 0 11"] = "e8d7";
	
	// en passant
	testPositions["k7/8/8/pP6/8/8/8/K7 w - a7a5 10 10"] =  "b5a6        a1a2 a1b2 a1b1 b5b6 "; // kings + en passant
    testPositions["k7/8/8/1Pp5/8/8/8/K7 w - c7c5 10 10"] =  "a1a2 a1b2 a1b1 b5b6 b5c6"; // kings + en passant

	clock_t start,end;
	cout << "TestGetLegalMoves ###########################" << endl;
	
	for (stdext::hash_map<string, string>::iterator item = testPositions.begin(); item != testPositions.end(); item++){
		{
			cout << endl << "FEN: " << item->first << endl;
			this->SetPosition(item->first);
			cout << "Status: ";
			if(IsCheck(currPos))
				cout << "check !!!" << endl;
			else 
				cout << "no check" << endl;

			start = clock();
			list<Move> moveList;
			GetLegalMoves(this->currPos, moveList);
			end = clock();
			cout << "found moves: ";
			DisplayMoves(moveList);
			cout << endl << "expected:    " << item->second;
			if(IsEqual(moveList, item->second)){
				cout << endl << " => --- OK ---";
			} else {
				cout << endl << " => ### KO ###";
			}
			cout << endl << "duration: " << ((double)end-(double)start) << " ticks" << endl;
		}
	
	}
	cout << endl <<  "TestGetLegalMoves ####################### END" << endl;
}

void Engine::DisplayMoves(list<Move>& moveList){
	if (debugMode)
    {
        cout << "###########################################" << endl;
        //foreach (ArrayBoardNode n in possibleNodes)
        for (list<ChildrenNode>::iterator node = possibleNodes.begin(); node != possibleNodes.end(); node++)
		{
            cout << move_to_uci(*(node->GetPreviousMove()),false) << " score=" << node->GetScore() << endl;
        }
        cout << "###########################################" <<endl;
    }
	for (list<Move>::iterator move = moveList.begin(); move != moveList.end(); move++){
		cout << move_to_uci(*move,false) << " ";
	}
}

bool Engine::IsEqual(std::list<Move>& moveList1, string& moveList2){
	istringstream is(moveList2);
	string move2;
	bool occurenceFound;
	int count = 0;
	while(is){
		is >> move2;
		count++;
		occurenceFound = false;
		for (list<Move>::iterator it = moveList1.begin(); it != moveList1.end(); it++)
		{
			string move1 = move_to_uci(*it,false);
			if(move1 == move2){
				occurenceFound = true;
			}
		}
		if(!occurenceFound) return false;
	}
	if((count-1)!=moveList1.size()) return false;

	return true;
}

void Engine::Start(){
	Threads.start_searching();

	//TODO: implement watch
	//watch.Start();
	list<Move> legalMoves;
	GetLegalMoves(currPos,legalMoves);
	InitPossibleNodes(legalMoves);

	if (brainMode == ADJUSTTIME_MODE) UpdateThinkDelay();
	//brainStatus = THINKING;
	ChessEngine.brainStatus = THINKING; //<-- global object
}

void Engine::Stop(){
	//brainStatus = PAUSE;
	//engine_brainStatus = PAUSE;
}

void Engine::Quit() {
	//brainStatus = QUITTING;
	ChessEngine.brainStatus = QUITTING;
}

void Engine::SetDebug(bool isDebugOn)
{
    debugMode = isDebugOn;
}

void Engine::SetSmartTiming(bool value)
{
    isSmartTiming = value;
}

void Engine::UpdateThinkDelay()
{
    int playerInc = 0;
    int playerTime = 0;
    int oppInc = 0;
    int oppTime = 0;
	if (currPos.turn)
    {
		/*
        playerInc = (int)winc.TotalMilliseconds;
        playerTime = (int)wtime.TotalMilliseconds;
        oppInc = (int)binc.TotalMilliseconds;
        oppTime = (int)btime.TotalMilliseconds;
		*/
    }
    else
    {
		/*
        playerInc = (int)this->binc.TotalMilliseconds;
        playerTime = (int)btime.TotalMilliseconds;
        oppInc = (int)winc.TotalMilliseconds;
        oppTime = (int)wtime.TotalMilliseconds;
		*/
    }
    int delay;

    // Smart timing : the engine adjust its think time depending on 
    // the time left for itself and the opponent
    // TODO: improve calculation
    // TODO: link calculation to current nodeStack/score state
    /*
	if (isSmartTiming)
    {
        if (playerTime > oppTime)
        {
            delay = (int)Math.Max(playerInc, playerTime / 20);
        }
        else
        {
            delay = (int)Math.Max(playerInc, playerTime / 20);
        }
    }
    else
    {*/
        delay = (int)max(playerInc, playerTime / 20);
    //}


    thinkDelay = delay;
    cout << "Think delay updated : " << thinkDelay << endl;
}

void Engine::UpdatePossibleNodesScores(int maxDepth)
{

	//for (int i = 0; i < possibleNodes.Count; i++) 
	for (list<ChildrenNode>::iterator node = possibleNodes.begin(); node != possibleNodes.end(); node++)
		node->UpdateScore(maxDepth, currPos.turn);
	if (isVerbose)
	{
	//possibleNodes.Sort();
		cout << "Node scores update ==================== DEBUG" << endl;
		//for (int i = 0; i < possibleNodes.Count; i++)
		for (list<ChildrenNode>::iterator node = possibleNodes.begin(); node != possibleNodes.end(); node++)
		{
			cout << move_to_uci(*(node->GetPreviousMove()),false) << " score=" << node->GetScore() <<endl;
		}
		cout << "END =================================== DEBUG" << endl;
	}

}

ChildrenNode* Engine::GetBestNode()
{
	if (currPos.turn)
    {
        return GetMaxNode(possibleNodes);
    }
    else
    {
        return GetMinNode(possibleNodes);
    }
}

ChildrenNode* Engine::GetMinNode(list<ChildrenNode>& possibleNodes)
{
    int minScore = 99999;
    ChildrenNode* minNode = NULL;
    //foreach (Node node in possibleNodes)
    for (list<ChildrenNode>::iterator node = possibleNodes.begin(); node != possibleNodes.end(); node++)
	{
        int score = node->GetScore();
        if (score < minScore)
        {
            minScore = score;
            minNode = &(*node);
        }
    }
    return minNode;
}

ChildrenNode* Engine::GetMaxNode(list<ChildrenNode>& possibleNodes)
{
    int maxScore = -99999;
    ChildrenNode* maxNode = NULL;
    //foreach (Node node in possibleNodes)
    for (list<ChildrenNode>::iterator node = possibleNodes.begin(); node != possibleNodes.end(); node++)
	{
        int score = node->GetScore();
        if (score > maxScore)
        {
            maxScore = score;
            maxNode = &(*node);
        }
    }
    return maxNode;
}

void Engine::GetCpuInfo() {
	// TODO: implement this method
	/*
	SYSTEM_INFO sysinfo;
	GetSystemInfo( &sysinfo );
	

	numCPU = sysinfo.dwNumberOfProcessors;
*/




}

void Engine::InitPossibleNodes(list<Move>& moves){
	
	while(ChessEngine.nodeQueue.size()>0) ChessEngine.nodeQueue.pop(); // TODO; see if there is a better option
	for (list<Move>::iterator move = moves.begin(); move != moves.end(); move++)
	{
		Position* tempPos = new Position(currPos);
		tempPos->Play(*move);
		ChildrenNode* tempNode = new ChildrenNode(*move,*tempPos,1);
		possibleNodes.push_back(*tempNode);
		nodeQueue.push(*tempNode);
		
		//NodeQueue = queue<Node>();
		ChessEngine.nodeQueue.push(*tempNode); // <-- new main Node queue
		// TODO: check if this update is needed
		tempNode->UpdateScore(1,currPos.turn);
	}

	this->maxDepth = 0;
	this->processedNodes = ChessEngine.nodeQueue.size();

}


void Engine::DisplayBestPve(int maxDepth)
{
	if (this->debugMode)
    {
        cout << "###########################################" << endl;
        //foreach (ArrayBoardNode n in possibleNodes)
        for (list<ChildrenNode>::iterator node = this->possibleNodes.begin(); node != this->possibleNodes.end(); node++)
		{
            cout << move_to_uci(*(node->GetPreviousMove()),false) << " score=" << node->GetScore() << endl;
        }
        cout << "###########################################" <<endl;
    }

	if (!this->possibleNodes.empty())
    {
        int pvDepth = 1;
        ChildrenNode* bestNode = this->GetBestNode();
        cout << "info currmove " << move_to_uci(*(bestNode->GetPreviousMove()),false) << endl;

		long time = this->watch.GetElapsedTime();
		
		int nodeNb = this->nodeQueue.size()+this->processedNodes;//+hashtable.Count;
        long nps = (time == 0) ? 0 : (1000 * nodeNb / time);
        //int hashfull = (hashMaxSize == 0) ? 0 : (1000 * hashtable.Count / hashMaxSize);
        //StringBuilder pv = new StringBuilder("info nodes " + nodeNb + " nps " + nps +/* " hashfull " + hashfull+*/  " depth " + maxDepth + " score cp " + bestNode.GetScore() + " time " + watch.ElapsedMilliseconds + " pv");
        ostringstream pv;
		cout << "MAXDEPTH=" << maxDepth << endl;
		pv << "info nodes " << nodeNb << " nps " << nps << " depth " << maxDepth << " score cp " << bestNode->GetScore() << " time " << time << " pv";
		while ((pvDepth < maxDepth) && (!bestNode->IsLeave()) /*bestNode != NULL*/)
        {
            pvDepth++;
            //pv.Append(" " + bestNode.GetPreviousMove().ToString());
			pv << " " << move_to_uci(*(bestNode->GetPreviousMove()),false);
            //if (engine->currPos.turn) // WRONG IMPLEMENTATION
			if(bestNode->position.turn)
            {
				bestNode = GetMaxNode(bestNode->children);
            }
            else
            {
				bestNode = GetMinNode(bestNode->children);
            }
        }
        //pv.Append(" " + bestNode.GetPreviousMove().ToString());
		pv << " " << move_to_uci(*(bestNode->GetPreviousMove()),false);
		cout << pv.str() << endl;
    }
}

ChildrenNode* Engine::GetMinNode(list<ChildrenNode*>* possibleNodes)
{
	if(possibleNodes == NULL) return NULL;
    int minScore = 99999;
    ChildrenNode* minNode = NULL;
    //foreach (Node node in possibleNodes)
    for (list<ChildrenNode*>::iterator node = possibleNodes->begin(); node != possibleNodes->end(); node++)
	{
        int score = (*node)->GetScore();
        if (score < minScore)
        {
            minScore = score;
            minNode = (*node);
        }
    }
    return minNode;
}

ChildrenNode* Engine::GetMaxNode(list<ChildrenNode*>* possibleNodes)
{
	if(possibleNodes == NULL) return NULL;
    int maxScore = -99999;
    ChildrenNode* maxNode = NULL;
    //foreach (Node node in possibleNodes)
    for (list<ChildrenNode*>::iterator node = possibleNodes->begin(); node != possibleNodes->end(); node++)
	{
        int score = (*node)->GetScore();
        if (score > maxScore)
        {
            maxScore = score;
            maxNode = (*node);
        }
    }
    return maxNode;
}